package simulator.software;

import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.Vector;

import simulator.hardware.Computer;

/**
 * Represents a single process operating in user space.
 * 
 * @author David LaPointe
 * @author Kyle Loomis
 * 
 */
public class Process implements ProcessorRunnable
{
   protected double blockProbability;
   protected int currentStep;
   protected ProcessState state = ProcessState.READY;
   protected Iterator<Step> stepIter = null;
   protected List<Step> steps = new Vector<Step>();
   protected String name = null;
   protected Computer computer = null;
   protected String statusDescriptor = null;
   protected int cyclesRunning;
   protected int cyclesBlocked;
   protected int cycleCompleted;
   protected int cyclesWaiting;
   protected int priority = 1;
   protected int adjustedPriority;

   /**
    * Creates a <code>Process</code> with the specified name, number of steps,
    * probability of blocking, and random seed.
    * 
    * @param name
    *           the name of this process.
    * @param stepCount
    *           the number of steps this process will run for.
    * @param blockProbability
    *           the probability that this process will block for I/O on any
    *           given step.
    * @param random
    *           the seed used to generate all random events within this process.
    */
   public Process(String name, int stepCount, double blockProbability,
         Random random)
   {
      this.name = name;
      this.blockProbability = blockProbability;

      if (blockProbability < 0 || blockProbability > 1)
      {
         throw new IllegalArgumentException(
               "Blocking probability must be a number between 0 and 1.");
      }

      if (stepCount <= 0)
      {
         throw new IllegalArgumentException(
               "A process must have more than 0 steps.");
      }

      statusDescriptor = "Ready";

      // generate list of steps
      for (int i = 0; i < stepCount; i++)
      {
         double r = random.nextDouble();

         if (r < blockProbability)
         {
            steps.add(Step.BLOCK);
         }
         else
         {
            steps.add(Step.RUN);
         }
      }

      stepIter = steps.iterator();
   }

   /**
    * Creates a <code>Process</code> with the specified name, number of steps,
    * probability of blocking, random seed, and priority.
    * 
    * @param name
    *           the name of this process.
    * @param stepCount
    *           the number of steps this process will run for.
    * @param blockProbability
    *           the probability that this process will block for I/O on any
    *           given step.
    * @param random
    *           the seed used to generate all random events within this process.
    * @param priority
    *           the numerical priority of this process, with lower integers
    *           representing lower priority.
    */
   public Process(String name, int stepCount, double blockProbability,
         Random random, int priority)
   {
      this(name, stepCount, blockProbability, random);
      this.setPriority(priority);
   }

   /**
    * Gets the adjusted priority of this process assigned by a priority
    * scheduling algorithm.
    * 
    * @return the adjusted priority of this process.
    */
   public int getAdjustedPriority()
   {
      return adjustedPriority;
   }

   /**
    * Gets the probability of this process blocking for I/O on any given step.
    * 
    * @return the probability of this process blocking for I/O on any given
    *         step.
    */
   public double getBlockProbability()
   {
      return blockProbability;
   }

   /**
    * Gets the current step (unit of time) this process has reached.
    * 
    * @return the current step (unit of time) this process has reached.
    */
   public int getCurrentStep()
   {
      return currentStep;
   }

   /**
    * Gets the priority of this process as assigned by a process scheduling
    * algorithm. If the current scheduling algorithm does not prioritize, this
    * method returns zero.
    * 
    * @return the assigned priority of this process.
    */
   public int getPriority()
   {
      return priority;
   }

   public ProcessState getState()
   {
      return state;
   }

   /**
    * Gets the number of steps that this process will run before completing.
    * 
    * @return the number of steps this process will run.
    */
   public int getTotalSteps()
   {
      return steps.size();
   }

   public void step()
   {
      cyclesRunning++;

      if (stepIter.hasNext())
      {
         currentStep++;
         Step step = stepIter.next();

         if (step == Step.BLOCK)
         {
            statusDescriptor = "Waiting for I/O...";
            computer.trap();
         }
         else
         {
            statusDescriptor = "Processing...";
         }
      }
      else
      {
         computer.processComplete();
      }
   }

   /**
    * Gets the name of the process.
    * 
    * @return the name of the process
    */
   public String getName()
   {
      return name;
   }

   /**
    * Sets the priority of this process to a temporary value. This method does
    * not affect the true priority of the process.
    * 
    * @param adjustedPriority
    *           the temporary priority value to assign to this process.
    */
   public void setAdjustedPriority(int adjustedPriority)
   {
      this.adjustedPriority = adjustedPriority;
   }

   /**
    * Sets the priority of this process.
    * 
    * @param priority
    *           the priority of this process.
    */
   public void setPriority(int priority)
   {
      this.priority = priority;
   }

   /**
    * Sets the current state of this process.
    * 
    * @param state
    *           the state of this process.
    */
   public void setState(ProcessState state)
   {
      this.state = state;

      if (this.state == ProcessState.READY)
      {
         statusDescriptor = "Ready";
      }
      else if (this.state == ProcessState.DONE)
      {
         statusDescriptor = "Complete";
      }
   }

   /**
    * Sets the computer on which this process exists.
    * 
    * @param computer
    *           the computer on which this process exists.
    */
   public void setComputer(Computer computer)
   {
      this.computer = computer;
   }

   public String getStatusDescriptor()
   {
      return statusDescriptor;
   }

   /**
    * Gets the number of cycles (steps) that this process has spent running.
    * 
    * @return the number of cycles (steps) spent running.
    */
   public int getCyclesRunning()
   {
      return cyclesRunning;
   }

   /**
    * Gets the number of cycles (steps) that this process has spent blocked
    * waiting for I/O.
    * 
    * @return the number of cycles (steps) spent blocked for I/O.
    */
   public int getCyclesBlocked()
   {
      return cyclesBlocked;
   }

   /**
    * Sets the number of cycles (steps) that this process has spent blocked
    * waiting for I/O.
    * 
    * @param cyclesBlocked
    *           the number of cycles (steps) this process has spent blocked.
    */
   public void setCyclesBlocked(int cyclesBlocked)
   {
      this.cyclesBlocked = cyclesBlocked;
   }

   /**
    * Sets the number of cycles (steps) that this process has completed.
    * 
    * @param cycleCompleted
    *           the number of cycles (steps) this process has completed.
    */
   public void setCycleCompleted(int cycleCompleted)
   {
      this.cycleCompleted = cycleCompleted;
   }

   /**
    * Sets the number of cycles (steps) that this process has spent in the ready
    * state.
    * 
    * @param cyclesWaiting
    *           the number of cycles this process has spent waiting in the ready
    *           state.
    */
   public void setCyclesWaiting(int cyclesWaiting)
   {
      this.cyclesWaiting = cyclesWaiting;
   }

   /**
    * Gets the number of cycles (steps) this process has spent in the ready
    * state.
    * 
    * @return the number of cycles this process has spent waiting in the ready
    *         state.
    */
   public int getCyclesWaiting()
   {
      return cyclesWaiting;
   }

   /**
    * Gets the number of cycles (steps) that this process has completed.
    * 
    * @return the number of cycles (steps) this process has completed.
    */
   public int getCycleCompleted()
   {
      return cycleCompleted;
   }
}
